/*
 * This file or a portion of this file is licensed under the terms of
 * the Globus Toolkit Public License, found in file ../GTPL, or at
 * http://www.globus.org/toolkit/download/license.html. This notice must
 * appear in redistributions of this file, with or without modification.
 *
 * Redistributions of this Software, with or without modification, must
 * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
 * some other similar material which is provided with the Software (if
 * any).
 *
 * Copyright 1999-2004 University of Chicago and The University of
 * Southern California. All rights reserved.
 */
package org.griphyn.vdl.dbschema;

import java.sql.*;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Vector;
import java.util.Enumeration;
import java.util.ArrayList;
import java.util.List;
import java.io.*;
import java.lang.reflect.*;
import java.sql.SQLException;
import edu.isi.pegasus.common.util.DynamicLoader;
import org.griphyn.vdl.util.ChimeraProperties;
import org.griphyn.vdl.util.FileHelper;
import org.griphyn.vdl.util.Logging;
import org.griphyn.vdl.util.LockFileLock;
import org.griphyn.vdl.classes.*;
import org.griphyn.vdl.parser.*;
import org.griphyn.vdl.dbdriver.*;

/**
 * This is a class that falls back not on a real database backend, but
 * rather on an existing Definitions data structure that are read from
 * file during construction (or rather, during open), and pushed back
 * into file at destruction (or rather, during close).<p>
 * While schemas in general should fall back onto drivers to perform
 * actions, it is rather difficult to create a JDBC interface to file
 * operations. Thus, the file operations are sneaked into this class.<p>
 * This class is thought more for experimental use than production.
 *
 * @author Jens-S. Vöckler
 * @author Yong Zhao
 * @version $Revision: 4507 $
 * @see org.griphyn.vdl.dbdriver
 * @see org.griphyn.vdl.classes.Definitions 
 */
public class SingleFileSchema extends InMemorySchema
{
  /**
   * Save the name of the database file, so we can dump our memory.
   */
  private String m_filename;

  /**
   * Save the file locking helper, once dynaloaded.
   */
  private FileHelper m_filehelper;

  /**
   * An instance of the VDLx XML parser.
   */
  private org.griphyn.vdl.parser.VDLxParser m_parser;

  /**
   * Fakes a connect to the database. This class load the memory
   * database during construction time from the specified file.
   *
   * @param hyphen_d is the CLI argument being passed, and ignored for now.
   */
  public SingleFileSchema( String hyphen_d )
    throws ClassNotFoundException, 
	   NoSuchMethodException, InstantiationException, 
	   IllegalAccessException, InvocationTargetException,
	   SQLException, IOException
  {
    super(); // call minimalistic c'tor, no driver loading!
    ChimeraProperties props = ChimeraProperties.instance();
    // Start a new set of definitions
    this.m_memory = new Definitions();
    this.m_dbschemaprops = 
	props.getDatabaseSchemaProperties( PROPERTY_PREFIX );

    // obtain the schema location URL from the schema properties: 
    // url is a list of strings representing schema locations. The
    // content exists in pairs, one of the namespace URI, one of the
    // location URL.
    String url = this.m_dbschemaprops.getProperty( "xml.url", 
					      props.getVDLSchemaLocation() );
    Logging.instance().log( "dbschema", 3, "schema=" + url );
    this.m_parser = new org.griphyn.vdl.parser.VDLxParser(url);
    Logging.instance().log( "dbschema", 3, "created reader" );

    // obtain the file location from the schema properties
    File db = new File( props.getSysConfDir(), "vds.db" );
    this.m_filename = 
      this.m_dbschemaprops.getProperty( "file.store", db.getAbsolutePath() );
    Logging.instance().log( "dbschema", 3, "filename=" + m_filename );

    // Determine helper to provide locking functions
    String locker = m_dbschemaprops.getProperty( "file.lock", "LockFileLock" );
    if ( locker.indexOf('.') == -1 ) locker = "org.griphyn.vdl.util." + locker;

    // dynamically load the file locking helper implementation
    Logging.instance().log( "dbschema", 3, "trying to load " + locker );
    DynamicLoader dl = new DynamicLoader(locker);
    String arg[] = new String[1];
    arg[0] = m_filename;
    m_filehelper = (FileHelper) dl.instantiate(arg);

    // FileHelper m_filehelper = new FileHelper2(this.m_filename);
    File file = m_filehelper.openReader();

    if ( file == null ) {
      Logging.instance().log( "dbschema", 3, "openReader returned null" );
      throw new SQLException( "Can't lock file " + this.m_memory );
    }

    // Does database exist?
    try {
      if ( file.exists() ) {
	// file exists, read it unless empty
	if ( file.length() > 0 ) {
	  // parse the complete file (database)
	  this.m_parser.parse( 
		new org.xml.sax.InputSource(
		new BufferedReader(
		new FileReader(file))),
		new NoHassleHandler(this.m_memory) );
	  Logging.instance().log( "app", 1, this.m_memory.getDefinitionCount() +
				  " definitions loaded into main memory" );
	}
      }
    } catch ( Exception e ) {
      throw new SQLException( e.getMessage() );
    } finally {
      // always release locks
      try { m_filehelper.closeReader(file); }
      catch ( Exception e ) { throw new SQLException( e.getMessage() ); }
    }
  }

  /**
   * Disassociate from the database driver before finishing. In this
   * case, dump the memory database back to the file that was saved.
   * Mind that performing this action may throw NullPointerException
   * in later stages!
   */
  public void close()
    throws SQLException
  {
    super.close();

    // FileHelper m_filehelper = new FileHelper2(this.m_filename);
    File file = m_filehelper.openWriter();

    if (file == null) {
      throw new SQLException("Unable to create file writer!");
    }
    
    try {
      BufferedWriter bw = new BufferedWriter(new FileWriter(file));
      this.m_memory.toXML(bw, "");
      bw.flush();
      bw.close();
    } catch ( IOException e ) {
      throw new SQLException( e.getMessage() );
    } finally {
      // always release locks
      try { m_filehelper.closeWriter(file); }
      catch ( Exception e ) { throw new SQLException( e.getMessage() ); }
    }
  }
}
